/*
 *	Wireless Tools
 *
 *		Jean II - HPL '01
 *
 * Just print the ESSID or NWID...
 *
 * This file is released under the GPL license.
 *     Copyright (c) 1997-2004 Jean Tourrilhes <jt@hpl.hp.com>
 */

#include "iwlib.h"		/* Header */

#include <getopt.h>

/*
 * Note on Pcmcia Schemes :
 * ----------------------
 *	The purpose of this tool is to use the ESSID discovery mechanism
 * to select the appropriate Pcmcia Scheme. The card tell us which
 * ESSID it has found, and we can then select the appropriate Pcmcia
 * Scheme for this ESSID (Wireless config (encrypt keys) and IP config).
 *	The way to do it is as follows :
 *			cardctl scheme "essidany"
 *			delay 100
 *			$scheme = iwgetid --scheme
 *			cardctl scheme $scheme
 *	Of course, you need to add a scheme called "essidany" with the
 * following setting :
 *			essidany,*,*,*)
 *				ESSID="any"
 *				IPADDR="10.0.0.1"
 *
 *	This can also be integrated int he Pcmcia scripts.
 *	Some drivers don't activate the card up to "ifconfig up".
 * Therefore, they wont scan ESSID up to this point, so we can't
 * read it reliably in Pcmcia scripts.
 *	I guess the proper way to write the network script is as follows :
 *			if($scheme == "iwgetid") {
 *				iwconfig $name essid any
 *				iwconfig $name nwid any
 *				ifconfig $name up
 *				delay 100
 *				$scheme = iwgetid $name --scheme
 *				ifconfig $name down
 *			}
 *
 *	This is pseudo code, but you get an idea...
 *	The "ifconfig up" activate the card.
 *	The "delay" is necessary to let time for the card scan the
 * frequencies and associate with the AP.
 *	The "ifconfig down" is necessary to allow the driver to optimise
 * the wireless parameters setting (minimise number of card resets).
 *
 *	Another cute idea is to have a list of Pcmcia Schemes to try
 * and to keep the first one that associate (AP address != 0). This
 * would be necessary for closed networks and cards that can't
 * discover essid...
 *
 * Jean II - 29/3/01
 */

/**************************** CONSTANTS ****************************/

#define FORMAT_DEFAULT	0	/* Nice looking display for the user */
#define FORMAT_SCHEME	1	/* To be used as a Pcmcia Scheme */
#define FORMAT_RAW	2	/* Raw value, for shell scripts */
#define WTYPE_ESSID	0	/* Display ESSID or NWID */
#define WTYPE_AP	1	/* Display AP/Cell Address */
#define WTYPE_FREQ	2	/* Display frequency/channel */
#define WTYPE_CHANNEL	3	/* Display channel (converted from freq) */
#define WTYPE_MODE	4	/* Display mode */
#define WTYPE_PROTO	5	/* Display protocol name */

/************************ DISPLAY ESSID/NWID ************************/

/*------------------------------------------------------------------*/
/*
 * Display the ESSID if possible
 */
    static int
print_essid(int			skfd,
        const char *	ifname,
        int			format)
{
    struct iwreq		wrq;
    char			essid[IW_ESSID_MAX_SIZE + 1];	/* ESSID */
    char			pessid[IW_ESSID_MAX_SIZE + 1];	/* Pcmcia format */
    unsigned int		i;
    unsigned int		j;

    /* Make sure ESSID is always NULL terminated */
    memset(essid, 0, sizeof(essid));

    /* Get ESSID */
    wrq.u.essid.pointer = (caddr_t) essid;
    wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
    wrq.u.essid.flags = 0;
    if(iw_get_ext(skfd, ifname, SIOCGIWESSID, &wrq) < 0)
        return(-1);

    switch(format)
    {
        case FORMAT_SCHEME:
            /* Strip all white space and stuff */
            j = 0;
            for(i = 0; i < strlen(essid); i++)
                if(isalnum(essid[i]))
                    pessid[j++] = essid[i];
            pessid[j] = '\0';
            if((j == 0) || (j > 32))
                return(-2);
            printf("%s\n", pessid);
            break;
        case FORMAT_RAW:
            printf("%s\n", essid);
            break;
        default:
            printf("%-8.16s  ESSID:\"%s\"\n", ifname, essid);
            break;
    }

    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the NWID if possible
 */
    static int
print_nwid(int		skfd,
        const char *	ifname,
        int		format)
{
    struct iwreq		wrq;

    /* Get network ID */
    if(iw_get_ext(skfd, ifname, SIOCGIWNWID, &wrq) < 0)
        return(-1);

    switch(format)
    {
        case FORMAT_SCHEME:
            /* Prefix with nwid to avoid name space collisions */
            printf("nwid%X\n", wrq.u.nwid.value);
            break;
        case FORMAT_RAW:
            printf("%X\n", wrq.u.nwid.value);
            break;
        default:
            printf("%-8.16s  NWID:%X\n", ifname, wrq.u.nwid.value);
            break;
    }

    return(0);
}

/**************************** AP ADDRESS ****************************/

/*------------------------------------------------------------------*/
/*
 * Display the AP Address if possible
 */
    static int
print_ap(int		skfd,
        const char *	ifname,
        int		format)
{
    struct iwreq		wrq;
    char			buffer[64];

    /* Get AP Address */
    if(iw_get_ext(skfd, ifname, SIOCGIWAP, &wrq) < 0)
        return(-1);

    /* Print */
    iw_ether_ntop((const struct ether_addr *) wrq.u.ap_addr.sa_data, buffer);
    switch(format)
    {
        case FORMAT_SCHEME:
            /* I think ':' are not problematic, because Pcmcia scripts
             * seem to handle them properly... */
        case FORMAT_RAW:
            printf("%s\n", buffer);
            break;
        default:
            printf("%-8.16s  Access Point/Cell: %s\n", ifname, buffer);
            break;
    }

    return(0);
}

/****************************** OTHER ******************************/

/*------------------------------------------------------------------*/
/*
 * Display the frequency (or channel) if possible
 */
    static int
print_freq(int		skfd,
        const char *	ifname,
        int		format)
{
    struct iwreq		wrq;
    double		freq;
    char			buffer[64];

    /* Get frequency / channel */
    if(iw_get_ext(skfd, ifname, SIOCGIWFREQ, &wrq) < 0)
        return(-1);

    /* Print */
    freq = iw_freq2float(&(wrq.u.freq));
    switch(format)
    {
        case FORMAT_SCHEME:
            /* Prefix with freq to avoid name space collisions */
            printf("freq%g\n", freq);
            break;
        case FORMAT_RAW:
            printf("%g\n", freq);
            break;
        default:
            iw_print_freq(buffer, sizeof(buffer), freq, -1, wrq.u.freq.flags);
            printf("%-8.16s  %s\n", ifname, buffer);
            break;
    }

    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the channel (converted from frequency) if possible
 */
    static int
print_channel(int		skfd,
        const char *	ifname,
        int		format)
{
    struct iwreq		wrq;
    struct iw_range	range;
    double		freq;
    int			channel;

    /* Get frequency / channel */
    if(iw_get_ext(skfd, ifname, SIOCGIWFREQ, &wrq) < 0)
        return(-1);

    /* Convert to channel */
    if(iw_get_range_info(skfd, ifname, &range) < 0)
        return(-2);
    freq = iw_freq2float(&(wrq.u.freq));
    if(freq < KILO)
        channel = (int) freq;
    else
    {
        channel = iw_freq_to_channel(freq, &range);
        if(channel < 0)
            return(-3);
    }

    /* Print */
    switch(format)
    {
        case FORMAT_SCHEME:
            /* Prefix with freq to avoid name space collisions */
            printf("channel%d\n", channel);
            break;
        case FORMAT_RAW:
            printf("%d\n", channel);
            break;
        default:
            printf("%-8.16s  Channel:%d\n", ifname, channel);
            break;
    }

    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the mode if possible
 */
    static int
print_mode(int		skfd,
        const char *	ifname,
        int		format)
{
    struct iwreq		wrq;

    /* Get frequency / channel */
    if(iw_get_ext(skfd, ifname, SIOCGIWMODE, &wrq) < 0)
        return(-1);
    if(wrq.u.mode >= IW_NUM_OPER_MODE)
        return(-2);

    /* Print */
    switch(format)
    {
        case FORMAT_SCHEME:
            /* Strip all white space and stuff */
            if(wrq.u.mode == IW_MODE_ADHOC)
                printf("AdHoc\n");
            else
                printf("%s\n", iw_operation_mode[wrq.u.mode]);
            break;
        case FORMAT_RAW:
            printf("%d\n", wrq.u.mode);
            break;
        default:
            printf("%-8.16s  Mode:%s\n", ifname, iw_operation_mode[wrq.u.mode]);
            break;
    }

    return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the ESSID if possible
 */
    static int
print_protocol(int		skfd,
        const char *	ifname,
        int		format)
{
    struct iwreq		wrq;
    char			proto[IFNAMSIZ + 1];	/* Protocol */
    char			pproto[IFNAMSIZ + 1];	/* Pcmcia format */
    unsigned int		i;
    unsigned int		j;

    /* Get Protocol name */
    if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
        return(-1);
    strncpy(proto, wrq.u.name, IFNAMSIZ);
    proto[IFNAMSIZ] = '\0';

    switch(format)
    {
        case FORMAT_SCHEME:
            /* Strip all white space and stuff */
            j = 0;
            for(i = 0; i < strlen(proto); i++)
                if(isalnum(proto[i]))
                    pproto[j++] = proto[i];
            pproto[j] = '\0';
            if((j == 0) || (j > 32))
                return(-2);
            printf("%s\n", pproto);
            break;
        case FORMAT_RAW:
            printf("%s\n", proto);
            break;
        default:
            printf("%-8.16s  Protocol Name:\"%s\"\n", ifname, proto);
            break;
    }

    return(0);
}

/******************************* MAIN ********************************/

/*------------------------------------------------------------------*/
/*
 * Check options and call the proper handler
 */
    static int
print_one_device(int		skfd,
        int		format,
        int		wtype,
        const char*	ifname)
{
    int ret;

    /* Check wtype */
    switch(wtype)
    {
        case WTYPE_AP:
            /* Try to print an AP */
            ret = print_ap(skfd, ifname, format);
            break;

        case WTYPE_CHANNEL:
            /* Try to print channel */
            ret = print_channel(skfd, ifname, format);
            break;

        case WTYPE_FREQ:
            /* Try to print frequency */
            ret = print_freq(skfd, ifname, format);
            break;

        case WTYPE_MODE:
            /* Try to print the mode */
            ret = print_mode(skfd, ifname, format);
            break;

        case WTYPE_PROTO:
            /* Try to print the protocol */
            ret = print_protocol(skfd, ifname, format);
            break;

        default:
            /* Try to print an ESSID */
            ret = print_essid(skfd, ifname, format);
            if(ret < 0)
            {
                /* Try to print a nwid */
                ret = print_nwid(skfd, ifname, format);
            }
    }

    return(ret);
}

/*------------------------------------------------------------------*/
/*
 * Try the various devices until one return something we can use
 *
 * Note : we can't use iw_enum_devices() because we want a different
 * behaviour :
 *	1) Stop at the first valid wireless device
 *	2) Only go through active devices
 */
    static int
scan_devices(int		skfd,
        int		format,
        int		wtype)
{
    char		buff[1024];
    struct ifconf ifc;
    struct ifreq *ifr;
    int		i;

    /* Get list of active devices */
    ifc.ifc_len = sizeof(buff);
    ifc.ifc_buf = buff;
    if(ioctl(skfd, SIOCGIFCONF, &ifc) < 0)
    {
        perror("SIOCGIFCONF");
        return(-1);
    }
    ifr = ifc.ifc_req;

    /* Print the first match */
    for(i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++)
    {
        if(print_one_device(skfd, format, wtype, ifr->ifr_name) >= 0)
            return 0;
    }
    return(-1);
}

/*------------------------------------------------------------------*/
/*
 * helper
 */
    static void
iw_usage(int status)
{
    fputs("Usage iwgetid [OPTIONS] [ifname]\n"
            "  Options are:\n"
            "    -a,--ap       Print the access point address\n"
            "    -c,--channel  Print the current channel\n"
            "    -f,--freq     Print the current frequency\n"
            "    -m,--mode     Print the current mode\n"
            "    -p,--protocol Print the protocol name\n"
            "    -r,--raw      Format the output as raw value for shell scripts\n"
            "    -s,--scheme   Format the output as a PCMCIA scheme identifier\n"
            "    -h,--help     Print this message\n",
            status ? stderr : stdout);
    exit(status);
}

static const struct option long_opts[] = {
    { "ap", no_argument, NULL, 'a' },
    { "channel", no_argument, NULL, 'c' },
    { "freq", no_argument, NULL, 'f' },
    { "mode", no_argument, NULL, 'm' },
    { "protocol", no_argument, NULL, 'p' },
    { "help", no_argument, NULL, 'h' },
    { "raw", no_argument, NULL, 'r' },
    { "scheme", no_argument, NULL, 's' },
    { NULL, 0, NULL, 0 }
};

/*------------------------------------------------------------------*/
/*
 * The main !
 */
    int
main(int	argc,
        char **	argv)
{
    int	skfd;			/* generic raw socket desc.	*/
    int	format = FORMAT_DEFAULT;
    int	wtype = WTYPE_ESSID;
    int	opt;
    int	ret = -1;

    /* Check command line arguments */
    while((opt = getopt_long(argc, argv, "acfhmprs", long_opts, NULL)) > 0)
    {
        switch(opt)
        {
            case 'a':
                /* User wants AP/Cell Address */
                wtype = WTYPE_AP;
                break;

            case 'c':
                /* User wants channel only */
                wtype = WTYPE_CHANNEL;
                break;

            case 'f':
                /* User wants frequency/channel */
                wtype = WTYPE_FREQ;
                break;

            case 'm':
                /* User wants the mode */
                wtype = WTYPE_MODE;
                break;

            case 'p':
                /* User wants the protocol */
                wtype = WTYPE_PROTO;
                break;

            case 'h':
                iw_usage(0);
                break;

            case 'r':
                /* User wants a Raw format */
                format = FORMAT_RAW;
                break;

            case 's':
                /* User wants a Scheme format */
                format = FORMAT_SCHEME;
                break;

            default:
                iw_usage(1);
                break;
        }
    }
    if(optind + 1 < argc) {
        fputs("Too many arguments.\n", stderr);
        iw_usage(1);
    }

    /* Create a channel to the NET kernel. */
    if((skfd = iw_sockets_open()) < 0)
    {
        perror("socket");
        return(-1);
    }

    /* Check if first argument is a device name */
    if(optind < argc)
    {
        /* Yes : query only this device */
        ret = print_one_device(skfd, format, wtype, argv[optind]);
    }
    else
    {
        /* No : query all devices and print first found */
        ret = scan_devices(skfd, format, wtype);
    }

    fflush(stdout);
    iw_sockets_close(skfd);
    return(ret);
}
